<!DOCTYPE html> <html lang=zh-cn style=--olcb-folder-code-block-max-height:80vh;--cnb-code-bg:rgb(245,245,245);--cnb-code-font-size:12px;--cnb-code-color:rgb(68,68,68) class><!--
 Page saved with SingleFile 
 url: https://www.cnblogs.com/feifeifeisir/p/14701066.html 
 saved date: Sun Mar 10 2024 15:04:05 GMT+0800 (GMT+08:00)
--><meta charset=utf-8>
<meta name=viewport content="width=device-width, initial-scale=1.0">
<meta name=description content="docxtpl 的简介 docxtpl 的作者开发出它的原因主要是python-docx擅长创建word文档，却不擅长修改。 对于docxtpl来说，使用MicrosoftWord编辑文档时，直接在文档中插入类似于Jinja2的标记。将文档保存为.docx文件(XML格式)：它将是.docx模板文件">
<meta property=og:description content="docxtpl 的简介 docxtpl 的作者开发出它的原因主要是python-docx擅长创建word文档，却不擅长修改。 对于docxtpl来说，使用MicrosoftWord编辑文档时，直接在文档中插入类似于Jinja2的标记。将文档保存为.docx文件(XML格式)：它将是.docx模板文件">
<meta http-equiv=Cache-Control content=no-transform>
<meta http-equiv=Cache-Control content=no-siteapp>
<meta http-equiv=X-UA-Compatible content="IE=edge">
<title>【docxtpl】python生成word2 - 傻白甜++ - 博客园</title>
<link rel=canonical href=https://www.cnblogs.com/feifeifeisir/p/14701066.html>
<style>#home :not(.cnblogs_code):not(.cnblogs_Highlighter)>pre:not([highlighted]):not([class*="brush:"]) code:not(.hljs),:not(.cnblogs_code):not(.cnblogs_Highlighter)>pre:not([highlighted]):not([class*="brush:"]) code:not(.hljs){background:rgb(245,245,245);padding:12px;border:0.909091px solid rgb(204,204,204);border-radius:3px;border-color:transparent;color:rgb(68,68,68);font-family:"Courier New",sans-serif;font-size:12px}</style><style>@media screen and (min-width:769px){}@media screen and (min-width:769px){}body{tab-size:4}img{max-width:100%;height:auto}#blog_post_info_block{margin-top:20px}#BlogPostCategory{margin-bottom:10px}.cnblogs-markdown :not(pre,div,td)>code,.blogpost-body :not(pre,div,td)>code{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace,sans-serif;font-size:12px;padding:0 5px;line-height:1.8;margin:0 3px;display:inline-block;overflow-x:auto;vertical-align:middle;border-radius:3px;background-color:#fbe5e1;color:#c0341d}code{font-family:inherit}.hljs-engine pre,.hljs-engine pre code{transition-duration:.2s;transition-property:background,font-size,border-color,border-radius,border-width,padding,margin,color}.blogpost-body pre code{line-height:1.5;margin:0}pre{overflow:auto}pre{margin-top:0;margin-bottom:0}#author_profile{float:left;width:280px;margin-top:0;margin-bottom:10px;color:#000;margin-left:0;font-size:12px}#author_profile a:hover{text-decoration:underline}.author_avatar{vertical-align:top;float:left;margin-right:5px;padding-top:5px;padding-left:2px;border:0}.author_profile_info{float:left;line-height:18px}#div_digg{float:right;margin-bottom:10px;margin-right:30px;font-size:12px;width:125px;text-align:center;margin-top:10px}.diggit{float:left;width:46px;height:52px;background:url()no-repeat;text-align:center;cursor:pointer;margin-top:2px;padding-top:5px}.diggnum{font-size:14px;color:#075db3;font-family:Verdana}.buryit{float:right;margin-left:20px;width:46px;height:52px;background:url()no-repeat;text-align:center;cursor:pointer;margin-top:2px;padding-top:5px}.burynum{font-size:14px;color:#075db3;font-family:Verdana}.diggword{margin-top:5px;margin-left:0;font-size:12px;color:#808080}#post_next_prev{line-height:1.8;font-size:12px}#post_next_prev a.p_n_p_prefix:link{text-decoration:none}#post_next_prev a.p_n_p_prefix:hover{text-decoration:underline}blockquote{border:2px solid #efefef;padding-left:10px;padding-right:10px;padding-top:5px;padding-bottom:5px;margin-top:10px;margin-bottom:10px}.blogpost-body blockquote{border-left:3px solid #e2dfdf;border-right:none;border-top:none;border-bottom:none;margin:10px 0;padding:5px 10px}#cnblogs_post_body blockquote{background-image:none}.under-post-card{margin-top:10px;line-height:1.5}#green_channel{padding:10px 0;margin-bottom:20px;margin-top:10px;border:#c0c0c0 1px dashed;font-size:12px;width:350px;text-align:center}#green_channel a{background:url(%3D%3D)repeat-x;display:inline-block;padding:3px 8px;color:#fff;text-decoration:none;font-weight:bold;cursor:pointer;margin-right:10px;-moz-border-radius:5px;-webkit-border-radius:10px;-moz-box-shadow:0 1px 3px rgba(0,0,0,.5);-webkit-box-shadow:0 1px 3px rgba(0,0,0,.5);text-shadow:0-1px 1px rgba(0,0,0,.25);vertical-align:middle}#green_channel a:hover{background-image:url()}#green_channel #green_channel_digg{background-color:#2daebf}#green_channel #green_channel_follow{background-color:#e33100}#green_channel #green_channel_favorite{background-color:#ffb515}#green_channel #green_channel_wechat{background-color:#4eab57}#cnblogs_post_body{margin-bottom:20px}#cnblogs_post_body li{list-style:inherit}#cnblogs_post_body ul{margin-left:30px;padding-left:0;list-style-type:disc}#cnblogs_post_body ol{padding-left:40px;list-style-type:decimal}#cnblogs_post_body table{border-collapse:collapse;word-break:break-word}#cnblogs_post_body .table-wrapper{display:block;overflow-x:auto}#cnblogs_post_body th{background-color:#fafafa}.blogpost-body th,.blogpost-body td,.blogpost-body th,.blogpost-body td{border:1px solid #c0c0c0;border-collapse:collapse;padding:8px 14px;min-width:50px}#cnblogs_post_body img{max-width:100%!important;height:auto}.ad_text_commentbox{margin-top:5px;margin-bottom:5px}#opt_under_post{line-height:1.8}#HistoryToday{max-width:600px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}#cnblogs_post_body{word-break:break-word}#cnblogs_post_body p{margin:10px auto}#cnblogs_post_body p{text-indent:0}#cnblogs_post_body h1{font-size:28px;font-weight:bold;line-height:1.5;margin:10px 0}#div_digg .diggnum{line-height:1.5em!important}#div_digg .burynum{line-height:1.5em!important}#green_channel a:link,#green_channel a:visited,#green_channel a:active{color:#fff!important;border:none!important}.vertical-middle>*{vertical-align:middle}@keyframes fadeIn{0%{opacity:0}100%{opacity:var(--cnb-code-toolbar-initial-opacity)}}pre:fullscreen,pre:fullscreen code{box-sizing:border-box;border-radius:0;height:100vh;width:100vw}.login_tips{background-image:url();background-repeat:no-repeat;margin-top:10px;padding:0 0 10px 25px;font-weight:400}.login_tips a{text-decoration:underline!important}#comment_nav{text-align:right;display:flex;align-items:center;width:100%}.comment-nav-left{margin-right:auto;margin-left:15px}.comment-nav-right{margin-left:auto}#comment_nav a{padding-left:10px}:root{--highlighted-line-bg:#d1e3c9;--highlighted-line-bg-dark:#264b33}:root{--cnblogs-current-collection-bg-color:rgba(245,245,245,.98);--cnblogs-current-collection-highlight-color:rgba(255,255,255,.9);--cnblogs-current-collection-hover-color:rgba(255,255,255,.7);--cnblogs-current-collection-active-color:rgba(255,255,255,.8);--cnblogs-current-collection-color:#000}@keyframes collapse{from{max-height:80vh;max-height:80vh;max-height:var(--olcb-folder-code-block-scroll-height,80vh)}to{max-height:80vh;max-height:80vh;max-height:var(--olcb-folder-code-block-max-height,80vh)}}@keyframes expand{from{max-height:80vh;max-height:80vh;max-height:var(--olcb-folder-code-block-max-height,80vh);overflow-y:hidden}to{max-height:"";max-height:"";max-height:var(--olcb-folder-code-block-scroll-height,"");overflow-y:hidden}}@keyframes modal-open{0%{height:0;opacity:0}95%{height:505px}100%{height:auto;opacity:1}}:root{--cnblogs-toc-bg-color:rgba(245,245,245,.98);--cnblogs-toc-highlight-color:rgba(255,255,255,.9);--cnblogs-toc-color:#000}@media only screen and (max-width:767px){}@keyframes wb-fade-in{0%{opacity:0}to{opacity:.85}}pre code.hljs{display:block;overflow-x:auto;padding:1em}.hljs{color:#444}.hljs-number{color:#800}/*!
 * ui-dialog.css
 * Date: 2014-07-03
 * https://github.com/aui/artDialog
 * (c) 2009-2014 TangBin, http://www.planeArt.cn
 *
 * This is licensed under the GNU LGPL, version 2.1 or later.
 * For details, see: http://www.gnu.org/licenses/lgpl-2.1.html
 */@-webkit-keyframes ui-dialog-loading{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes ui-dialog-loading{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}</style>
<style>*{margin:0;padding:0}html{height:100%}body{color:#000;background:#d2d8de;font-family:"PingFang SC","Microsoft YaHei","Helvetica Neue","Helvetica","Arial",sans-serif;font-size:14px;min-height:101%}table{border-spacing:0}img{border:0}ul{word-break:break-all}li{list-style:none}h2{font-size:100%;font-weight:normal}a:link{color:#000;text-decoration:none}a:visited{color:#000;text-decoration:none}a:hover{color:#f60;text-decoration:underline}a:active{color:#000;text-decoration:none}.clear{clear:both}#home{margin:0 auto}#blogTitle{height:80px;background:#fff;clear:both}#blogTitle h1{font-size:150%;font-weight:bold;line-height:1.5;margin-left:1em;margin-top:10px;width:50%;float:left}#blogTitle h2{margin-left:4em;line-height:1.5;width:50%;float:left}#blogLogo{float:right}#navigator{background-color:#000;height:50px;line-height:50px;overflow:hidden;clear:both}#navList{min-height:30px;float:left}#navList li{float:left}#navList a{display:block;padding:0 1.5em;height:60px;font-family:"å¾®è½¯é›…é»‘";float:left;font-size:1.2em;text-align:center;transition-duration:.3s}#navList a:link,#navList a:visited,#navList a:active{color:#bbb}#navList a:hover{color:#fff;background-color:#343434;text-decoration:none;text-shadow:0 0 1px #000}.blogStats{float:right;color:#ccc;padding-right:10px;text-align:right}#main{min-width:950px;text-align:left;padding:20px 0 0 10px;overflow:hidden}#mainContent .forFlow{margin-left:8em;margin-right:8em;width:auto}#mainContent{min-height:200px;padding:0 0 10px 0;-o-text-overflow:ellipsis;text-overflow:ellipsis;overflow:hidden;word-break:break-all;float:right;margin-left:-21em;margin-right:20px;display:inline;width:100%}.postTitle{clear:both}.postTitle a:link,.postTitle a:visited,.postTitle a:active{color:#390;transition:all .4s linear 0s}.postTitle a:hover{color:#f60;text-decoration:none;margin-left:10px}.postDesc{clear:both;line-height:2.5em}.postDesc a:link,.postDesc a:visited,.postDesc a:active{color:#666}.postDesc a:hover{color:#f60;text-decoration:none}#topics{min-height:200px;margin-bottom:15px;padding:15px;-o-text-overflow:ellipsis;text-overflow:ellipsis;overflow:hidden;word-break:break-all;background:#fff;border-radius:7px;box-shadow:1px 1px 2px #a7a8ad}#topics .postTitle{font-size:130%;font-weight:bold;border-bottom:1px solid #999;float:left;line-height:2em;width:100%;padding-left:5px}#topics .postDesc{float:right;width:100%;text-align:right;padding-right:5px;color:#666;margin-top:5px}#footer{text-align:center;min-height:15px;border-top:1px solid #000;margin-top:10px;padding-top:10px;margin-bottom:10px}.postBody{padding:15px 2px 5px 5px;line-height:1.8;color:#000;border-bottom:1px solid #ccc}.postBody a:link,.postBody a:visited,.postBody a:active{text-decoration:underline}.postBody li{list-style-type:disc;margin-bottom:1em}.postBody blockquote{background:url()no-repeat 25px 0;line-height:1.6;color:#333}#cnblogs_post_body img{max-width:820px}</style>
<style>.hljs{background:#f5f5f5;font-family:"Courier New",sans-serif;font-size:12px;border:1px solid #ccc;border-radius:3px}.hljs-comment{color:#008000}.hljs-keyword,.hljs-selector-tag,.hljs-built_in,.hljs-name,.hljs-tag{color:#00f}.hljs-string,.hljs-title,.hljs-attribute,.hljs-literal{color:#a31515}.hljs-meta{color:#2b91af}.hljs-attr{color:#f00}.hljs-symbol{color:#00b0e8}</style>
<style>#cnblogs_post_body{color:black;font:0.875em/1.5em"微软雅黑","PTSans","Arial",sans-serif;font-size:15px}#cnblogs_post_body h2{background:#008eb7;border-radius:6px 6px 6px 6px;box-shadow:0 0 0 1px #5F5A4B,1px 1px 6px 1px rgba(10,10,0,0.5);font-family:"微软雅黑","宋体","黑体",Arial;font-size:20px;font-weight:bold;height:25px;line-height:25px;margin:18px 0!important;padding:8px 0 5px 5px;text-shadow:2px 2px 3px #222222}#cnblogs_post_body h3{background:#399ab2;border-radius:6px 6px 6px 6px;box-shadow:0 0 0 1px #5F5A4B,1px 1px 6px 1px rgba(10,10,0,0.5);font-family:"微软雅黑","宋体","黑体",Arial;font-size:18px;font-weight:bold;height:25px;line-height:25px;margin:18px 0!important;padding:8px 0 5px 5px;text-shadow:2px 2px 3px #222222}#cnblogs_post_body h2 a:hover{color:rgb(255,102,0)}#cnblogs_post_body h1:hover{color:rgb(255,102,0)}#cnblogs_post_body h2{color:rgb(235,235,235)}#cnblogs_post_body h2:hover{color:rgb(255,102,0)}#cnblogs_post_body h3{color:rgb(235,235,235)}#cnblogs_post_body h3:hover{color:rgb(255,102,0)}#back-to-top{background-color:#00CD00;bottom:0;box-shadow:0 0 6px #00CD00;color:#444444;padding:10px 10px;position:fixed;right:50px;cursor:pointer}</style>
<style media="only screen and (max-width: 767px)">body{font-size:14px!important;-webkit-text-size-adjust:none}.forpc{display:none!important}#blog_nav_rss{display:none!important}#blog_nav_newpost{display:none!important}.blogpost-body img{max-width:300px!important;height:auto}.blogpost-body p>img{display:block;margin:auto}#green_channel{width:100%}#cnblogs_post_body table{display:block;overflow-x:scroll;-webkit-overflow-scrolling:regular;max-height:800px}#cnblogs_post_body table::-webkit-scrollbar:horizontal{height:12px}#cnblogs_post_body table::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);border-radius:10px;background-color:#f5f5f5}#cnblogs_post_body table::-webkit-scrollbar{width:12px;background-color:#f5f5f5}#cnblogs_post_body table::-webkit-scrollbar-thumb{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);background-color:#555}.commentform{margin-left:10px}.cnblogs-markdown :not(pre,div,td)>code,.blogpost-body :not(pre,div,td)>code{white-space:pre-wrap}#main{min-width:auto!important;padding-left:0!important;width:auto!important;padding-right:5px}#navigator{width:auto!important}#mainContent{margin-left:0!important;float:none!important}#mainContent .forFlow{margin-left:5px!important}#comment_form{padding-left:10px!important}#blogTitle h1{width:auto!important}#home{width:auto;min-width:unset}</style>
<link type=application/rss+xml rel=alternate href=https://www.cnblogs.com/feifeifeisir/rss>
<link type=application/rsd+xml rel=EditURI href=https://www.cnblogs.com/feifeifeisir/rsd.xml>
<link type=application/wlwmanifest+xml rel=wlwmanifest href=https://www.cnblogs.com/feifeifeisir/wlwmanifest.xml>
<style>.medium-zoom-image{cursor:zoom-in;transition:transform 300ms cubic-bezier(0.2,0,0.2,1)!important}</style>
<meta property=og:image content=https://img-blog.csdnimg.cn/img_convert/d1e06ca23805a1a930a9925a9c93a95f.png><meta http-equiv=origin-trial content=AymqwRC7u88Y4JPvfIF2F37QKylC04248hLCdJAsh8xgOfe/dVJPV3XS3wLFca1ZMVOtnBfVjaCMTVudWM//5g4AAAB7eyJvcmlnaW4iOiJodHRwczovL3d3dy5nb29nbGV0YWdtYW5hZ2VyLmNvbTo0NDMiLCJmZWF0dXJlIjoiUHJpdmFjeVNhbmRib3hBZHNBUElzIiwiZXhwaXJ5IjoxNjk1MTY3OTk5LCJpc1RoaXJkUGFydHkiOnRydWV9><meta name=referrer content=no-referrer><link id=favicon rel="shortcut icon" href="" type=image/svg+xml><style>.sf-hidden{display:none!important}</style><meta http-equiv=content-security-policy content="default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:; object-src 'self' data:; frame-src 'self' data:;"><style>img[src="data:,"],source[src="data:,"]{display:none!important}</style></head>
<body class="skin-darkgreentrip no-navbar hljs-engine">
 <a name=top></a>
 <div id=top_nav class="navbar forpc navbar-custom sf-hidden">
 
 </div>
 
 
<div id=home class>
<div id=header>
 
 <div id=navigator class>
 
<ul id=navList>
<li>
</li>
<li>

</li>
<li>

</li>
<li>
</li>
<li>

</li>
<li>

</li>
</ul>
 <div class=blogStats>
 



 </div>
 </div>
</div>
<div id=main class>
 <div id=mainContent class>
 <div class=forFlow>
 <div id=post_detail>
 
 <div id=topics class>
 <div class=post>
 <h1 class=postTitle>
 
<a id=cb_post_title_url class="postTitle2 vertical-middle" href=https://www.cnblogs.com/feifeifeisir/p/14701066.html>
 <span role=heading aria-level=2>【docxtpl】python生成word2</span>
 
</a><button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button>
 </h1>
 <div class=clear></div>
 <div class=postBody>
 <div id=cnblogs_post_body class="blogpost-body blogpost-body-html">
<div id=article_content class="article_content clearfix">
<div id=content_views class="markdown_views prism-atom-one-dark">
<blockquote class>
<p class>&nbsp;</p>
</blockquote>
<h1 class><a id=docxtpl__13 rel=noopener></a>docxtpl 的简介<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h1>
<p class>docxtpl 的作者开发出它的原因主要是python-docx擅长创建word文档，却不擅长修改。</p>
<p class>对于docxtpl来说，使用MicrosoftWord编辑文档时，直接在文档中插入类似于Jinja2的标记。将文档保存为.docx文件(XML格式)：它将是.docx模板文件。</p>
<p class>然后使用docxtpl加载这个.docx模板，按照Jinja2的语法传入关联的上下文变量，即可生成想要的Word文档，多次传入不同的上下文变量即可生成多个基于模板的word文档。</p>
<p class>docxtpl 主要依赖两个包</p>
<ul class>
<li class>python-docx ：读写word文档</li>
<li class>jinja2：管理插入到模板中的标签</li>
</ul>
<p class>jinja2语法可参考：<br> <a href=http://docs.jinkan.org/docs/jinja2/templates.html rel=noopener>http://docs.jinkan.org/docs/jinja2/templates.html</a><br> docxtpl官方文档：<br> <a href=https://docxtpl.readthedocs.io/en/latest/ rel=noopener>https://docxtpl.readthedocs.io/en/latest/</a></p>
<p class>安装：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-mipsasm">pip <span class=hljs-keyword>install </span>docxtpl
</code></pre>
<p class>基本使用示例：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs"><span class=hljs-keyword>from</span> docxtpl <span class=hljs-keyword>import</span> DocxTemplate
tpl = DocxTemplate(<span class=hljs-string>"my_word_template.docx"</span>)
context = { <span class=hljs-string>'company_name'</span> : <span class=hljs-string>"World company"</span> }
tpl.render(context)
tpl.save(<span class=hljs-string>"generated_doc.docx"</span>)
</code></pre>
<h1 class><a id=docxtpl_51 rel=noopener></a>docxtpl的使用手册<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h1>
<h2 class><a id=Jinja2_53 rel=noopener></a>Jinja2语法<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h2>
<p class>为了掌握docxtpl 的用法，我们需要学习或复习一下Jinja2的语法，然后再研究docxtpl 特有的类Jinja2语法。</p>
<p class><strong>基本语法：</strong></p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-html highlighter-hljs hljs language-xml">{% if user %}
	<span class=hljs-tag>&lt;<span class=hljs-name>title</span>&gt;</span> hello {{user}} <span class=hljs-tag>&lt;/<span class=hljs-name>title</span>&gt;</span>
{% else %}
	<span class=hljs-tag>&lt;<span class=hljs-name>title</span>&gt;</span> welcome to docxtpl <span class=hljs-tag>&lt;/<span class=hljs-name>title</span>&gt;</span>        
{% endif %}
<span class=hljs-tag>&lt;<span class=hljs-name>ul</span>&gt;</span>
    {% for index in indexs %}
    <span class=hljs-tag>&lt;<span class=hljs-name>li</span>&gt;</span> {{ index }} <span class=hljs-tag>&lt;/<span class=hljs-name>li</span>&gt;</span>
    {% endfor %}
<span class=hljs-tag>&lt;/<span class=hljs-name>ul</span>&gt;</span>
</code></pre>
<p>在模板中{{ variable }}结构表示变量，是一种特殊的占位符，告诉模板引擎这个位置的值，从渲染模板时使用的数据中获取；Jinja2除了能识别基本类型的变量，还能识别字典；</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-html highlighter-hljs hljs language-xml"><span class=hljs-tag>&lt;<span class=hljs-name>p</span>&gt;</span>{{mydict['key']}}<span class=hljs-tag>&lt;/<span class=hljs-name>p</span>&gt;</span>

<span class=hljs-tag>&lt;<span class=hljs-name>p</span>&gt;</span>{{mylist[1]}}<span class=hljs-tag>&lt;/<span class=hljs-name>p</span>&gt;</span>

<span class=hljs-tag>&lt;<span class=hljs-name>p</span>&gt;</span>{{mylist[myvariable]}}<span class=hljs-tag>&lt;/<span class=hljs-name>p</span>&gt;</span>
</code></pre>
<p class>过滤器的本质就是函数，使用方式为：变量名 | 过滤器。 过滤器名写在变量名后面，中间用 | 分隔。如：{{variable | capitalize}}，这个过滤器的作用：把变量variable的值的首字母转换为大写，其他字母转换为小写。 操作列表的常用过滤器如下：</p>
<p class><strong>列表操作：</strong></p>
<p class><strong class>first：取第一个元素</strong></p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-html highlighter-hljs hljs language-xml">  <span class=hljs-tag>&lt;<span class=hljs-name>p</span>&gt;</span>{{ [1,2,3,4,5,6] | first }}<span class=hljs-tag>&lt;/<span class=hljs-name>p</span>&gt;</span>
</code></pre>
<p class><strong class>last：取最后一个元素</strong></p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-html highlighter-hljs hljs language-xml">  <span class=hljs-tag>&lt;<span class=hljs-name>p</span>&gt;</span>{{ [1,2,3,4,5,6] | last }}<span class=hljs-tag>&lt;/<span class=hljs-name>p</span>&gt;</span>
</code></pre>
<p class><strong class>length：获取列表长度</strong></p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-html highlighter-hljs hljs language-xml">  <span class=hljs-tag>&lt;<span class=hljs-name>p</span>&gt;</span>{{ [1,2,3,4,5,6] | length }}<span class=hljs-tag>&lt;/<span class=hljs-name>p</span>&gt;</span>
</code></pre>
<p class><strong>sum：列表求和</strong></p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-html highlighter-hljs hljs language-xml">  <span class=hljs-tag>&lt;<span class=hljs-name>p</span>&gt;</span>{{ [1,2,3,4,5,6] | sum }}<span class=hljs-tag>&lt;/<span class=hljs-name>p</span>&gt;</span>
</code></pre>
<p><strong>sort：列表排序</strong></p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-html highlighter-hljs hljs language-xml">  <span class=hljs-tag>&lt;<span class=hljs-name>p</span>&gt;</span>{{ [6,2,3,1,5,4] | sort }}<span class=hljs-tag>&lt;/<span class=hljs-name>p</span>&gt;</span>
</code></pre>
<h2><a id=Jinja_116 rel=noopener></a>Jinja自定义过滤器<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h2>
<p class>render() 的 jinja_env 选项参数可以传递一个jinja环境对象，从而添加一些定制的jinja过滤器：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs"><span class=hljs-keyword>from</span> docxtpl <span class=hljs-keyword>import</span> DocxTemplate
<span class=hljs-keyword>import</span> jinja2
<span class=hljs-keyword>def</span> <span class="hljs-title function_">multiply_by</span>(<span class=hljs-params>value, by</span>):
   <span class=hljs-keyword>return</span> value * by

tpl = DocxTemplate(<span class=hljs-string>"my_word_template.docx"</span>)
context = { <span class=hljs-string>'price_dollars'</span> : <span class=hljs-number>5.00</span> }
jinja_env = jinja2.Environment()
jinja_env.filters[<span class=hljs-string>'multiply_by'</span>] = multiply_by
tpl.render(context,jinja_env)
tpl.save(<span class=hljs-string>"generated_doc.docx"</span>)
</code></pre>
<p>然后在模板中能够使用</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-yaml"><span class=hljs-attr>Euros price :</span> {{ <span class=hljs-string>price_dollars|multiply_by(0.88)</span> }}
</code></pre>
<h2 class><a id=Jinja_2_140 rel=noopener></a>类Jinja 2语法<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h2>
<h3><a id=4_142 rel=noopener></a>4个重要的专属标签<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p class>为了管理段落、表行、表列、run，必须使用特殊的语法：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-css">{%<span class=hljs-selector-tag>p</span> jinja2_tag %} for paragraphs
{%<span class=hljs-selector-tag>tr</span> jinja2_tag %} for <span class=hljs-selector-tag>table</span> rows
{%tc jinja2_tag %} for <span class=hljs-selector-tag>table</span> <span class=hljs-attribute>columns</span>
{%r jinja2_tag %} for runs
</code></pre>
<p>正常的Jinja 2语法只有%的普通标签，而docxtpl的类语法包含%p，%tr，%tc，%r</p>
<p>%p：段落，即docx.text.paragraph.Paragraph对象</p>
<p>%tr：表格中的一行，即docx.table._Row对象</p>
<p>%tc：表格中的一列，即docx.table._Column对象</p>
<p>%r：段落中的一个片段，即docx.text.run.Run对象</p>
<p>通过使用这些标记，python-docx-template将真正的Jinja 2标记放入文档的XML源代码中的正确位置。</p>
<p>另外，需注意，这四种标签，起始标签不能在同一行，必须在不同的行上面。</p>
<p>例如：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-perl">{%p <span class=hljs-keyword>if</span> display_paragraph %}Here is <span class=hljs-keyword>my</span> paragraph {%p endif %}
</code></pre>
<p>需改写成：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-perl">{%p <span class=hljs-keyword>if</span> display_paragraph %}
Here is <span class=hljs-keyword>my</span> paragraph
{%p endif %}
</code></pre>
<p>否则无法正确渲染。</p>
<h3><a id=_183 rel=noopener></a>长文本拆分<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p>包含jinja2标签的文本如果太长，可能无法读取：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-armasm"><span class=hljs-symbol>My</span> house is located {% <span class=hljs-meta>if</span> living_in_town %} in urban <span class=hljs-meta>area</span> {% <span class=hljs-meta>else</span> %} in countryside {% <span class=hljs-meta>endif</span> %} <span class=hljs-keyword>and</span> I love it.
</code></pre>
<p>可以使用 <code>{%-</code> 和<code>-%}</code> 标签来拆分整个文本：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-armasm"><span class=hljs-symbol>My</span> house is located
{%- <span class=hljs-meta>if</span> living_in_town -%}
 in urban <span class=hljs-meta>area</span>
{%- <span class=hljs-meta>else</span> -%}
 in countryside
{%- <span class=hljs-meta>endif</span> -%}
 <span class=hljs-keyword>and</span> I love it.
</code></pre>
<blockquote>
<p class><strong>注意：</strong> <code>{%- xxx -%}</code> 标签必须在单独在一行中，不可在之前或之后添加其他的文本。</p>
</blockquote>
<p>Jinja 2的语法对变量是使用双括弧：{{ variable }}</p>
<p class>但如果variable 是RichText对象时，必须更改为：{{r variable }}</p>
<blockquote class>
<p><strong>注意</strong>：</p>
<ol>
<li class>r紧跟左括弧。variable不能使用r作为变量，因为{{r}} 可以解释为 {{r 没有指定变量</li>
<li>不要在同一run上使用2次 <code>{{r</code> . 使用 RichText.add() 方法在python代码上连接多个字符串和样式，在word文档模板上只需 写一个 <code>{{r</code> 标签。</li>
</ol></blockquote>
<h3 class><a id=_216 rel=noopener></a>表格处理与合并单元格<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p class><strong>列跨越：</strong></p>
<p class>如果需要动态生成一个合并单元格，可以使用colspan标记：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-erlang">{<span class=hljs-comment>% colspan column_count %}</span>
</code></pre>
<p>column_count是一个整数表示要跨越的列数。</p>
<p>例如，word模板中：</p>
<div class=table-wrapper><table>
<thead>
<tr><th>{% colspan col_labels|count %}Type of thing<th>&nbsp;<th>&nbsp;</tr>
</thead>
<tbody>
<tr>
<td>{%tc for col in col_labels %}</td>
<td>{{ col }}</td>
<td>{%tc endfor %}</td>
</tr>
</tbody>
</table></div>
<p class>python渲染代码：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">tpl.render({<span class=hljs-string>'col_labels'</span>: [<span class=hljs-string>'fruit'</span>, <span class=hljs-string>'vegetable'</span>, <span class=hljs-string>'stone'</span>, <span class=hljs-string>'thing'</span>]})
</code></pre>
<p class>渲染结果：</p>
<p class><img src="" alt=image-20201231100326466 class=medium-zoom-image></p>
<p><strong>水平合并单元格：</strong></p>
<p>或者在for循环中：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-erlang">{<span class=hljs-comment>% hm %}</span>
</code></pre>
<p>例如，word模板：</p>
<div class=table-wrapper><table>
<thead>
<tr><th>{%tc for x in [1, 2, 3, 4] %}<th>{% hm %}Header 2<th>{%tc endfor %}</tr>
</thead>
<tbody>
<tr>
<td>{%tc for x in [1, 2, 3, 4] %}</td>
<td>Subheader {{ x }}</td>
<td>{%tc endfor %}</td>
</tr>
</tbody>
</table></div>
<p>最终渲染结果：</p>
<p class><img src="" alt=image-20201231100716535 class=medium-zoom-image></p>
<p><strong>垂直合并单元格：</strong></p>
<p>在for循环中：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-erlang">{<span class=hljs-comment>% vm %}</span>
</code></pre>
<p class>例如，word模板：</p>
<div class=table-wrapper><table>
<thead>
<tr><th>Description<th>Quantity<th>Price<th>&nbsp;</tr>
</thead>
<tbody>
<tr>
<td>{%tr for i in items %}</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>{% vm %}{{category}}</td>
<td class>{{ i.desc }}</td>
<td>{{ i.qty }}</td>
<td>{{ i.price }}</td>
</tr>
<tr>
<td>{%tr endfor %}</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>Total</td>
<td class>&nbsp;</td>
<td>{{total_price}}</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table></div>
<p>python渲染代码：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">context = {
    <span class=hljs-string>'items'</span>: [
        {<span class=hljs-string>'desc'</span>: <span class=hljs-string>'Python interpreters'</span>, <span class=hljs-string>'qty'</span>: <span class=hljs-number>2</span>, <span class=hljs-string>'price'</span>: <span class=hljs-string>'FREE'</span>},
        {<span class=hljs-string>'desc'</span>: <span class=hljs-string>'Django projects'</span>, <span class=hljs-string>'qty'</span>: <span class=hljs-number>5403</span>, <span class=hljs-string>'price'</span>: <span class=hljs-string>'FREE'</span>},
        {<span class=hljs-string>'desc'</span>: <span class=hljs-string>'Guido'</span>, <span class=hljs-string>'qty'</span>: <span class=hljs-number>1</span>, <span class=hljs-string>'price'</span>: <span class=hljs-string>'100,000,000.00'</span>},
    ],
    <span class=hljs-string>'total_price'</span>: <span class=hljs-string>'100,000,000.00'</span>,
    <span class=hljs-string>'category'</span>: <span class=hljs-string>'Book'</span>,
}

tpl.render(context)
</code></pre>
<p class>渲染结果：</p>
<p class><img src= alt=image-20201231100927973 class=medium-zoom-image></p>
<h3 class><a id=RichText_301 rel=noopener></a>RichText动态样式<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p>使用{{ variable }}标记，它将保持目前的格式使用variable 变量的值进行字符串替换。但如果要添加动态变化的样式，则必须使用{{r variable }}标记，同时传入的variable变量是一个 RichText对象。RichText对象可以在python代码中更改颜色、粗体、斜体、大小等。</p>
<p>使用{{r variable }}标记时，它在docx模板中原本的样式将会被删除，如果没有在RichText()设置字体样式，样式将返回到Microsoft Word默认样式。</p>
<p>还可以通过Richtext将超链接添加到文本中：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">tpl=DocxTemplate(<span class=hljs-string>'your_template.docx'</span>)
rt = RichText(<span class=hljs-string>'You can add an hyperlink, here to '</span>)
rt.add(<span class=hljs-string>'google'</span>,url_id=tpl.build_url_id(<span class=hljs-string>'http://google.com'</span>))
</code></pre>
<p>RichText(‘my text’)的简写是R(‘my text’)</p>
<p>python代码示例：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs"><span class=hljs-keyword>from</span> docxtpl <span class=hljs-keyword>import</span> DocxTemplate, RichText

tpl = DocxTemplate(<span class=hljs-string>'templates/richtext_and_if_tpl.docx'</span>)

context = {<span class=hljs-string>'foobar'</span>: RichText(<span class=hljs-string>'Foobar!'</span>, color=<span class=hljs-string>'ff0000'</span>)}
tpl.render(context)
tpl.save(<span class=hljs-string>'output/richtext_and_if.docx'</span>)
</code></pre>
<p>richtext_and_if_tpl.docx文件模板内容：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-powershell">{%<span class=hljs-built_in>r</span> <span class=hljs-keyword>if</span> foobar %} {{<span class=hljs-built_in>r</span> foobar }}COUCOU{%<span class=hljs-built_in>r</span> endif %}
</code></pre>
<p>渲染结果：</p>
<p><img src="" alt=image-20201231103317949 class=medium-zoom-image></p>
<h3><a id=_345 rel=noopener></a>单元格颜色<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p>需要更改表格单元格的背景色时，必须在单元格的开头放置以下标记</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-erlang">{<span class=hljs-comment>% cellbg variable %}</span>
</code></pre>
<p>variable 必须是颜色的十六进制码，不支持red等颜色单词。</p>
<p>比如模板：</p>
<div class=table-wrapper><table>
<thead>
<tr><th class>Date<th>Type<th>Alert Description</tr>
</thead>
<tbody>
<tr>
<td>{%tr for a in alerts %}</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td class>{{ a.date }}</td>
<td>{% cellbg a.bg %}{{ a.type }}</td>
<td>{{r a.desc }}</td>
</tr>
<tr>
<td>{%tr endfor %}</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table></div>
<p>python渲染代码：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">context = {
    <span class=hljs-string>'alerts'</span>: [
        {
            <span class=hljs-string>'date'</span>: <span class=hljs-string>'2015-03-10'</span>,
            <span class=hljs-string>'desc'</span>: RichText(<span class=hljs-string>'Very critical alert'</span>, color=<span class=hljs-string>'FF0000'</span>, bold=<span class=hljs-literal>True</span>),
            <span class=hljs-string>'type'</span>: <span class=hljs-string>'CRITICAL'</span>,
            <span class=hljs-string>'bg'</span>: <span class=hljs-string>'FF0000'</span>,
        },
        {
            <span class=hljs-string>'date'</span>: <span class=hljs-string>'2015-03-11'</span>,
            <span class=hljs-string>'desc'</span>: RichText(<span class=hljs-string>'Just a warning'</span>),
            <span class=hljs-string>'type'</span>: <span class=hljs-string>'WARNING'</span>,
            <span class=hljs-string>'bg'</span>: <span class=hljs-string>'FFDD00'</span>,
        },
        {
            <span class=hljs-string>'date'</span>: <span class=hljs-string>'2015-03-12'</span>,
            <span class=hljs-string>'desc'</span>: RichText(<span class=hljs-string>'Information'</span>),
            <span class=hljs-string>'type'</span>: <span class=hljs-string>'INFO'</span>,
            <span class=hljs-string>'bg'</span>: <span class=hljs-string>'8888FF'</span>,
        },
        {
            <span class=hljs-string>'date'</span>: <span class=hljs-string>'2015-03-13'</span>,
            <span class=hljs-string>'desc'</span>: RichText(<span class=hljs-string>'Debug trace'</span>),
            <span class=hljs-string>'type'</span>: <span class=hljs-string>'DEBUG'</span>,
            <span class=hljs-string>'bg'</span>: <span class=hljs-string>'FF00FF'</span>,
        },
    ],
}

tpl.render(context)
</code></pre>
<p>渲染结果：</p>
<p><img src= alt=image-20201231101202701 class=medium-zoom-image></p>
<h3><a id=Escaping_406 rel=noopener></a>Escaping和转义<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p>为了展示<code>{%</code>, <code>%}</code>, <code>{{</code> or <code>}}</code>, 可以使用：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-perl">{<span class=hljs-number>_</span>%, %_}, {<span class=hljs-number>_</span>{ <span class=hljs-keyword>or</span>  }<span class=hljs-number>_</span>}
</code></pre>
<p>传入 <code>{{ variable }}</code>的variable 变量不能使用<code>&lt;</code>, <code>&gt;</code> 和<code>&amp;</code>等字符，必须转义它们，否则会导致整个文档错乱。</p>
<p>转义方法有 :</p>
<ul>
<li><code>context = { 'variable':R('my text') }</code> 和在模板中<code>{{r variable }}</code></li>
<li><code>context = { 'variable':'my text'}</code>和在模板中<code>{{ variable|e }}</code></li>
<li><code>context = { 'var':escape('my text')}</code> 和在模板中<code>{{ &lt;var&gt; }}</code></li>
<li>在调用渲染方法时启用自动转义：<code>tpl.render(context, autoescape=True)</code> (默认值autoescape=False)</li>
</ul>
<p class>RichText()或R()支持换行符，新段落，缩进和分页符功能：只需使用<code>\n</code>, <code>\a</code>, <code>\t</code> 或<code>\f</code> 在文本中，它们将作相应的转换。</p>
<p>示例：</p>
<p>word模板内容：</p>
<pre class=highlighter-hljs highlighted=true><code class="highlighter-hljs hljs language-powershell">{{orgin}}

{{<span class=hljs-built_in>r</span> var1}}

{{var2|e}}

{{var3}}

{{ var4}}
</code></pre>
<p>python渲染脚本：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">context = {
    <span class=hljs-string>'var1'</span>: R(
        <span class=hljs-string>'&lt;&gt;&amp;：必须被转义才能显示, 可以使用RichText() 或 R()'</span>
    ),
    <span class=hljs-string>'var2'</span>: <span class=hljs-string>'或者使用 "|e" jinja 过滤器来转义 &lt;&gt;&amp; '</span>,
    <span class=hljs-string>'var3'</span>: escape(<span class=hljs-string>'或者使用escape函数来转义: &lt;&gt;&amp; ...'</span>),
    <span class=hljs-string>'var4'</span>: <span class=hljs-string>'多行文本\n\ttab缩进和一些段落\n会自动\a被转换'</span>,
}

tpl.render(context)
</code></pre>
<p>渲染结果：</p>
<p class><img src="" alt=image-20201231123219404 class=medium-zoom-image></p>
<p>假如不转义，直接传入这些特殊字符：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">context = {
    <span class=hljs-string>'orgin'</span>: <span class=hljs-string>'直接传入&lt;&gt;&amp;看看渲染结果：'</span>,
    <span class=hljs-string>'var1'</span>: R(
        <span class=hljs-string>'&lt;&gt;&amp;：必须被转义才能显示, 可以使用RichText() 或 R()'</span>
    ),
    <span class=hljs-string>'var2'</span>: <span class=hljs-string>'或者使用 "|e" jinja 过滤器来转义 &lt;&gt;&amp; '</span>,
    <span class=hljs-string>'var3'</span>: escape(<span class=hljs-string>'或者使用escape函数来转义: &lt;&gt;&amp; ...'</span>),
    <span class=hljs-string>'var4'</span>: <span class=hljs-string>'多行文本\n\ttab缩进和一些段落\n会自动\a被转换'</span>,
}

tpl.render(context)
</code></pre>
<p class>渲染结果：</p>
<p><img src="" alt=image-20201231124708705 class=medium-zoom-image></p>
<p>结果就是会导致其他已经转义的<code>&lt;&gt;&amp;</code>等字符都无法正常显示。</p>
<p>如果希望可以直接安全的转入这些字符，可以开启自动转义：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">context = {
    <span class=hljs-string>'orgin'</span>: <span class=hljs-string>'&lt;&gt;&amp;没有转义的情况下无法显示'</span>,
    <span class=hljs-string>'var1'</span>: R(
        <span class=hljs-string>'&lt;&gt;&amp;：必须被转义才能显示, 可以使用RichText() 或 R()'</span>
    ),
    <span class=hljs-string>'var2'</span>: <span class=hljs-string>'或者使用 "|e" jinja 过滤器来转义 &lt;&gt;&amp; '</span>,
    <span class=hljs-string>'var3'</span>: escape(<span class=hljs-string>'或者使用escape函数来转义: &lt;&gt;&amp; ...'</span>),
    <span class=hljs-string>'var4'</span>: <span class=hljs-string>'多行文本\n\ttab缩进和一些段落\n会自动\a被转换'</span>,
}

tpl.render(context, autoescape=<span class=hljs-literal>True</span>)
</code></pre>
<p>渲染结果：</p>
<p><img src= alt=image-20201231125141374 class=medium-zoom-image></p>
<h2><a id=docxtpl2_504 rel=noopener></a>docxtpl的2个高级对象<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h2>
<h3 class><a id=_506 rel=noopener></a>内嵌图像<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p class>doxtpl.inlineImage对象可以动态地将一个或多个图像添加到文档中(支持JPEG和PNG格式)。</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs"><span class=hljs-keyword>from</span> docx.shared <span class=hljs-keyword>import</span> Mm

myimage=InlineImage(tpl,<span class=hljs-string>'python_logo.png'</span>,width=Mm(<span class=hljs-number>20</span>))
</code></pre>
<p class>对于高度和宽度，必须使用毫米(毫米)，英寸(英寸)或点(Pt)类。</p>
<p class><strong>示例</strong></p>
<p class>word模板：</p>
<p><img src="" alt=image-20201231141341612 class=medium-zoom-image></p>
<p class>python渲染代码：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs"><span class=hljs-keyword>from</span> docxtpl <span class=hljs-keyword>import</span> DocxTemplate, InlineImage

<span class=hljs-comment># for height and width you have to use millimeters (Mm), inches or points(Pt) class :</span>
<span class=hljs-keyword>from</span> docx.shared <span class=hljs-keyword>import</span> Mm
<span class=hljs-keyword>import</span> jinja2

tpl = DocxTemplate(<span class=hljs-string>'templates/inline_image_tpl.docx'</span>)

context = {
    <span class=hljs-string>'myimage'</span>: InlineImage(tpl, <span class=hljs-string>'templates/python_logo.png'</span>, width=Mm(<span class=hljs-number>20</span>)),
    <span class=hljs-string>'myimageratio'</span>: InlineImage(
        tpl, <span class=hljs-string>'templates/python_jpeg.jpg'</span>, width=Mm(<span class=hljs-number>30</span>), height=Mm(<span class=hljs-number>60</span>)
    ),
    <span class=hljs-string>'frameworks'</span>: [
        {
            <span class=hljs-string>'image'</span>: InlineImage(tpl, <span class=hljs-string>'templates/django.png'</span>, height=Mm(<span class=hljs-number>10</span>)),
            <span class=hljs-string>'desc'</span>: <span class=hljs-string>'The web framework for perfectionists with deadlines'</span>,
        },
        {
            <span class=hljs-string>'image'</span>: InlineImage(tpl, <span class=hljs-string>'templates/zope.png'</span>, height=Mm(<span class=hljs-number>10</span>)),
            <span class=hljs-string>'desc'</span>: <span class=hljs-string>'Zope is a leading Open Source Application Server and Content Management Framework'</span>,
        },
        {
            <span class=hljs-string>'image'</span>: InlineImage(tpl, <span class=hljs-string>'templates/pyramid.png'</span>, height=Mm(<span class=hljs-number>10</span>)),
            <span class=hljs-string>'desc'</span>: <span class=hljs-string>'Pyramid is a lightweight Python web framework aimed at taking small web apps into big web apps.'</span>,
        },
        {
            <span class=hljs-string>'image'</span>: InlineImage(tpl, <span class=hljs-string>'templates/bottle.png'</span>, height=Mm(<span class=hljs-number>10</span>)),
            <span class=hljs-string>'desc'</span>: <span class=hljs-string>'Bottle is a fast, simple and lightweight WSGI micro web-framework for Python'</span>,
        },
        {
            <span class=hljs-string>'image'</span>: InlineImage(tpl, <span class=hljs-string>'templates/tornado.png'</span>, height=Mm(<span class=hljs-number>10</span>)),
            <span class=hljs-string>'desc'</span>: <span class=hljs-string>'Tornado is a Python web framework and asynchronous networking library.'</span>,
        },
    ],
}
<span class=hljs-comment># testing that it works also when autoescape has been forced to True</span>
jinja_env = jinja2.Environment(autoescape=<span class=hljs-literal>True</span>)
tpl.render(context, jinja_env)
tpl.save(<span class=hljs-string>'output/inline_image.docx'</span>)
</code></pre>
<p class>渲染结果：</p>
<p><img src="" alt=image-20201231141524121 class=medium-zoom-image></p>
<h3 class><a id=_575 rel=noopener></a>子文件<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p class>Subdoc对象可以作为一个使用python-docx库从头开始构建word文档的对象，构建的内容可以直接嵌入到传入的变量位置：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">sd = tpl.new_subdoc()
p = sd.add_paragraph(<span class=hljs-string>'This is a sub-document inserted into a bigger one'</span>)
p = sd.add_paragraph(<span class=hljs-string>'It has been '</span>)
p.add_run(<span class=hljs-string>'dynamically'</span>).style = <span class=hljs-string>'dynamic'</span>
p.add_run(<span class=hljs-string>' generated with python by using '</span>)
p.add_run(<span class=hljs-string>'python-docx'</span>).italic = <span class=hljs-literal>True</span>
p.add_run(<span class=hljs-string>' library'</span>)

context = {
    <span class=hljs-string>'mysubdoc'</span>: sd,
}
tpl.render(context)
</code></pre>
<p class>效果：</p>
<p><img src="" alt=image-20201231141843340 class=medium-zoom-image></p>
<h2 class><a id=word_600 rel=noopener></a>替换word文档的图片或媒体<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h2>
<p class>在页眉/页脚中是无法动态添加图片和媒体的，但我们可以在模板中放置一个虚拟对象，像往常一样渲染模板，然后用另一个对象替换虚拟对象。从而实现图片和媒体的添加。</p>
<p class>替换可以发生在页眉、页脚和整个文档正文中。</p>
<h3 class><a id=_606 rel=noopener></a>替换页眉/页脚中的图片<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p class>需要先在页眉/页脚中放置一张模板图片，替换时指定被插入的模板图片的文件名即可，替换dummy_header_pic.jpg的语法：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">tpl.replace_pic(<span class=hljs-string>'dummy_header_pic.jpg'</span>,<span class=hljs-string>'header_pic_i_want.jpg'</span>)
</code></pre>
<p class>被替换的图像在word文档中将保持原始文档的宽高比。</p>
<h3 class><a id=_618 rel=noopener></a>替换页眉/页脚中的媒体<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p>与替换图片几乎一致，替换dummy_header_pic.jpg的语法：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">tpl.replace_media(<span class=hljs-string>'dummy_header_pic.jpg'</span>,<span class=hljs-string>'header_pic_i_want.jpg'</span>)
</code></pre>
<p class>警告：与replace_pic() 方法不同，dummy_header_pic.jpg 必须存在模板目录中。</p>
<h3 class><a id=_630 rel=noopener></a>替换嵌入对象<button class="cnblogs-toc-button sf-hidden" title=显示目录导航 aria-expanded=false></button></h3>
<p class>它的工作方式类似于媒体替换，只是它适用于嵌入式docx这样的嵌入式对象。</p>
<p class>替换embedded_dummy.docx的语法：</p>
<pre class=highlighter-hljs highlighted=true><code class="prism language-python highlighter-hljs hljs">tpl.replace_embedded(<span class=hljs-string>'embdded_dummy.docx'</span>,<span class=hljs-string>'embdded_docx_i_want.docx'</span>)
</code></pre>
<p class>警告：与replace_pic()方法不同，embdded_dumy.docx必须存在于模板目录中。</p>
</div>
</div>
</div>
<div class=clear></div>

 </div>
 
 </div>
 
 
 </div>
</div>
<a id=!comments></a>
<div id=blog-comments-placeholder class=sf-hidden></div>
<div id=comment_form class=commentform>
 <a name=commentform></a>
 <div id=divCommentShow></div>
 
 <div id=comment_form_container>
</div>
 <div class=ad_text_commentbox id=ad_text_under_commentbox></div>
 <div id=cnblogs_ch class=sf-hidden></div>
 <div id=opt_under_post></div>
 <div id=cnblogs_c1 class="under-post-card sf-hidden">
 
 </div>
 <div id=under_post_card1></div>
 <div id=under_post_card2></div>
 <div id=HistoryToday class=under-post-card></div>
 
</div>
 </div>
 </div>
 
 <div class=clear></div>
 </div>
 <div class=clear></div>
 
</div>
 <div id=page_end_html>
 

 </div>
 
 
<div style=z-index:998;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);display:none;flex-direction:column;align-items:center;justify-content:center>
 
 </div><style class=single-file-highlights-stylesheet>.single-file-highlight-yellow, .single-file-highlight-yellow-mode ::selection { background-color: #ffff7c !important; color: black !important; } .single-file-highlight-pink, .single-file-highlight-pink-mode ::selection { background-color: #ffbbb6 !important; color: black !important; } .single-file-highlight-blue, .single-file-highlight-blue-mode ::selection { background-color: #95d0ff !important; color: black !important; } .single-file-highlight-green, .single-file-highlight-green-mode ::selection { background-color: #93ef8d !important; color: black !important; } span.single-file-highlight-yellow, span.single-file-highlight-pink, span.single-file-highlight-blue, span.single-file-highlight-green { display: inline !important; } .single-file-highlight-hidden { background-color: inherit !important; color: inherit !important; } .single-file-mask { all: initial; display: contents !important; } .single-file-mask.single-file-page-mask { opacity: .99 !important; } single-file-note { all: initial !important; display: contents !important; } .single-file-cut-hover, .single-file-cut-outer-hover, .single-file-cut-selected, .single-file-cut-outer-selected { transition: outline-width 125ms !important; outline-offset: -4px !important; outline-width: 4px !important; } .single-file-cut-hover, .single-file-cut-outer-hover { outline-style: dotted !important; } .single-file-cut-selected, .single-file-cut-outer-selected { outline-style: dashed !important; } .single-file-cut-hover, .single-file-cut-selected { outline-color: red !important; } .single-file-cut-outer-hover, .single-file-cut-outer-selected { outline-color: green !important; } .single-file-cut-mode, .single-file-cut-mode * { pointer-events: auto !important; touch-action: none !important; } .single-file-cut-hover, .single-file-cut-outer-hover, .single-file-remove-highlights-mode .single-file-highlight:hover { cursor: crosshair !important; } .single-file-removed { display: none !important; float: none !important; position: static !important; visibility: collapse !important; } a[href], img { -webkit-touch-callout: none; }</style><script data-template-shadow-root>(() => { document.currentScript.remove(); const processNode = node => { node.querySelectorAll("template[shadowrootmode]").forEach(element=>{ let shadowRoot = getShadowRoot(element.parentElement); if (!shadowRoot) { try { shadowRoot = element.parentElement.attachShadow({mode:element.getAttribute("shadowrootmode")}); shadowRoot.innerHTML = element.innerHTML; element.remove(); } catch (error) {} if (shadowRoot) { processNode(shadowRoot); } } }) }; const FORBIDDEN_TAG_NAMES = ["a","area","audio","base","br","col","command","embed","hr","img","iframe","input","keygen","link","meta","param","source","track","video","wbr"]; const NOTE_TAGNAME = "single-file-note"; const NOTE_CLASS = "note"; const NOTE_ANCHORED_CLASS = "note-anchored"; const NOTE_SELECTED_CLASS = "note-selected"; const NOTE_MOVING_CLASS = "note-moving"; const NOTE_MASK_MOVING_CLASS = "note-mask-moving"; const MASK_CLASS = "single-file-mask"; const HIGHLIGHT_CLASS = "single-file-highlight"; const NOTES_WEB_STYLESHEET = ".note { all: initial; display: flex; flex-direction: column; height: 150px; width: 150px; position: absolute; top: 10px; left: 10px; border: 1px solid rgb(191, 191, 191); z-index: 2147483646; box-shadow: 3px 3px 3px rgba(33, 33, 33, .7); min-height: 100px; min-width: 100px; } .note-selected { z-index: 2147483647; } .note-hidden { display: none; } .note-collapsed { min-height: 30px; max-height: 30px; overflow: hidden; } .note blockquote { all: initial; padding: 1px; height: 100%; } .note textarea { all: initial; white-space: break-spaces; font-family: Arial, Helvetica, sans-serif; font-size: 14px; height: 100%; width: 100%; padding: 2px; border: 1px solid transparent; resize: none; color: black; } .note textarea:focus { border: 1px dotted rgb(160, 160, 160); } .note header { all: initial; min-height: 30px; cursor: grab; user-select: none; } .note .note-remove { all: initial; position: absolute; right: 0px; top: 2px; padding: 5px; opacity: .5; cursor: pointer; user-select: none; width: 16px; height: 16px; } .note .note-anchor { all: initial; position: absolute; left: 0px; top: 2px; padding: 5px; opacity: .25; cursor: pointer; width: 16px; height: 16px; } .note .note-resize { all: initial; position: absolute; bottom: -5px; right: -5px; height: 15px; width: 15px; cursor: nwse-resize; user-select: none; } .note .note-remove:hover { opacity: 1; } .note .note-anchor:hover { opacity: .5; } .note-anchored .note-anchor { opacity: .5; } .note-anchored .note-anchor:hover { opacity: 1; } .note-moving { opacity: .75; box-shadow: 6px 6px 3px rgba(33, 33, 33, .7); } .note-moving * { cursor: grabbing; } .note-yellow header { background-color: #f5f545; } .note-yellow blockquote { background-color: #ffff7c; } .note-pink header { background-color: #ffa59f; } .note-pink blockquote { background-color: #ffbbb6; } .note-blue header { background-color: #84c8ff; } .note-blue blockquote { background-color: #95d0ff; } .note-green header { background-color: #93ef8d; } .note-green blockquote { background-color: #9cff95; }"; const MASK_WEB_STYLESHEET = ".note-mask { all: initial; position: fixed; z-index: 2147483645; pointer-events: none; background-color: transparent; transition: background-color 125ms; } .note-mask-moving.note-yellow { background-color: rgba(255, 255, 124, .3); } .note-mask-moving.note-pink { background-color: rgba(255, 187, 182, .3); } .note-mask-moving.note-blue { background-color: rgba(149, 208, 255, .3); } .note-mask-moving.note-green { background-color: rgba(156, 255, 149, .3); } .page-mask { all: initial; position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483646; } .page-mask-active { width: 100vw; height: 100vh; }"; const NOTE_HEADER_HEIGHT = 25; const PAGE_MASK_ACTIVE_CLASS = "page-mask-active"; const REMOVED_CONTENT_CLASS = "single-file-removed"; const reflowNotes = function reflowNotes() { document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => { const noteElement = containerElement.shadowRoot.querySelector("." + NOTE_CLASS); const noteBoundingRect = noteElement.getBoundingClientRect(); const anchorElement = getAnchorElement(containerElement); const anchorBoundingRect = anchorElement.getBoundingClientRect(); const maxX = anchorBoundingRect.x + Math.max(0, anchorBoundingRect.width - noteBoundingRect.width); const minX = anchorBoundingRect.x; const maxY = anchorBoundingRect.y + Math.max(0, anchorBoundingRect.height - NOTE_HEADER_HEIGHT); const minY = anchorBoundingRect.y; let left = parseInt(noteElement.style.getPropertyValue("left")); let top = parseInt(noteElement.style.getPropertyValue("top")); if (noteBoundingRect.x > maxX) { left -= noteBoundingRect.x - maxX; } if (noteBoundingRect.x < minX) { left += minX - noteBoundingRect.x; } if (noteBoundingRect.y > maxY) { top -= noteBoundingRect.y - maxY; } if (noteBoundingRect.y < minY) { top += minY - noteBoundingRect.y; } noteElement.style.setProperty("position", "absolute"); noteElement.style.setProperty("left", left + "px"); noteElement.style.setProperty("top", top + "px"); }); }; const addNoteRef = function addNoteRef(anchorElement, noteId) { const noteRefs = getNoteRefs(anchorElement); noteRefs.push(noteId); setNoteRefs(anchorElement, noteRefs); }; const deleteNoteRef = function deleteNoteRef(containerElement, noteId) { const anchorElement = getAnchorElement(containerElement); const noteRefs = getNoteRefs(anchorElement).filter(noteRefs => noteRefs != noteId); if (noteRefs.length) { setNoteRefs(anchorElement, noteRefs); } else { delete anchorElement.dataset.singleFileNoteRefs; } }; const getNoteRefs = function getNoteRefs(anchorElement) { return anchorElement.dataset.singleFileNoteRefs ? anchorElement.dataset.singleFileNoteRefs.split(" ") : []; }; const setNoteRefs = function setNoteRefs(anchorElement, noteRefs) { anchorElement.dataset.singleFileNoteRefs = noteRefs.join(" "); }; const getAnchorElement = function getAnchorElement(containerElement) { return document.querySelector("[data-single-file-note-refs~=\"" + containerElement.dataset.noteId + "\"]") || document.documentElement; }; const getMaskElement = function getMaskElement(className, containerClassName) { let maskElement = document.documentElement.querySelector("." + className); if (!maskElement) { maskElement = document.createElement("div"); const maskContainerElement = document.createElement("div"); if (containerClassName) { maskContainerElement.classList.add(containerClassName); } maskContainerElement.classList.add(MASK_CLASS); const firstNote = document.querySelector(NOTE_TAGNAME); if (firstNote && firstNote.parentElement == document.documentElement) { document.documentElement.insertBefore(maskContainerElement, firstNote); } else { document.documentElement.appendChild(maskContainerElement); } maskElement.classList.add(className); const maskShadow = maskContainerElement.attachShadow({ mode: "open" }); maskShadow.appendChild(getStyleElement(MASK_WEB_STYLESHEET)); maskShadow.appendChild(maskElement); return maskElement; } }; const getStyleElement = function getStyleElement(stylesheet) { const linkElement = document.createElement("style"); linkElement.textContent = stylesheet; return linkElement; }; const attachNoteListeners = function attachNoteListeners(containerElement, editable = false) { const SELECT_PX_THRESHOLD = 4; const COLLAPSING_NOTE_DELAY = 750; const noteShadow = containerElement.shadowRoot; const noteElement = noteShadow.childNodes[1]; const headerElement = noteShadow.querySelector("header"); const mainElement = noteShadow.querySelector("textarea"); const noteId = containerElement.dataset.noteId; const resizeElement = noteShadow.querySelector(".note-resize"); const anchorIconElement = noteShadow.querySelector(".note-anchor"); const removeNoteElement = noteShadow.querySelector(".note-remove"); mainElement.readOnly = !editable; if (!editable) { anchorIconElement.style.setProperty("display", "none", "important"); } else { anchorIconElement.style.removeProperty("display"); } headerElement.ontouchstart = headerElement.onmousedown = event => { if (event.target == headerElement) { collapseNoteTimeout = setTimeout(() => { noteElement.classList.toggle("note-collapsed"); hideMaskNote(); }, COLLAPSING_NOTE_DELAY); event.preventDefault(); const position = getPosition(event); const clientX = position.clientX; const clientY = position.clientY; const boundingRect = noteElement.getBoundingClientRect(); const deltaX = clientX - boundingRect.left; const deltaY = clientY - boundingRect.top; maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS); document.documentElement.style.setProperty("user-select", "none", "important"); anchorElement = getAnchorElement(containerElement); displayMaskNote(); selectNote(noteElement); moveNote(event, deltaX, deltaY); movingNoteMode = { event, deltaX, deltaY }; document.documentElement.ontouchmove = document.documentElement.onmousemove = event => { clearTimeout(collapseNoteTimeout); if (!movingNoteMode) { movingNoteMode = { deltaX, deltaY }; } movingNoteMode.event = event; moveNote(event, deltaX, deltaY); }; } }; resizeElement.ontouchstart = resizeElement.onmousedown = event => { event.preventDefault(); resizingNoteMode = true; selectNote(noteElement); maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS); document.documentElement.style.setProperty("user-select", "none", "important"); document.documentElement.ontouchmove = document.documentElement.onmousemove = event => { event.preventDefault(); const { clientX, clientY } = getPosition(event); const boundingRectNote = noteElement.getBoundingClientRect(); noteElement.style.width = clientX - boundingRectNote.left + "px"; noteElement.style.height = clientY - boundingRectNote.top + "px"; }; }; anchorIconElement.ontouchend = anchorIconElement.onclick = event => { event.preventDefault(); noteElement.classList.toggle(NOTE_ANCHORED_CLASS); if (!noteElement.classList.contains(NOTE_ANCHORED_CLASS)) { deleteNoteRef(containerElement, noteId); addNoteRef(document.documentElement, noteId); } onUpdate(false); }; removeNoteElement.ontouchend = removeNoteElement.onclick = event => { event.preventDefault(); deleteNoteRef(containerElement, noteId); containerElement.remove(); }; noteElement.onmousedown = () => { selectNote(noteElement); }; function moveNote(event, deltaX, deltaY) { event.preventDefault(); const { clientX, clientY } = getPosition(event); noteElement.classList.add(NOTE_MOVING_CLASS); if (editable) { if (noteElement.classList.contains(NOTE_ANCHORED_CLASS)) { deleteNoteRef(containerElement, noteId); anchorElement = getTarget(clientX, clientY) || document.documentElement; addNoteRef(anchorElement, noteId); } else { anchorElement = document.documentElement; } } document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host); noteElement.style.setProperty("left", (clientX - deltaX) + "px"); noteElement.style.setProperty("top", (clientY - deltaY) + "px"); noteElement.style.setProperty("position", "fixed"); displayMaskNote(); } function displayMaskNote() { if (anchorElement == document.documentElement || anchorElement == document.documentElement) { hideMaskNote(); } else { const boundingRectAnchor = anchorElement.getBoundingClientRect(); maskNoteElement.classList.add(NOTE_MASK_MOVING_CLASS); if (selectedNote) { maskNoteElement.classList.add(selectedNote.dataset.color); } maskNoteElement.style.setProperty("top", (boundingRectAnchor.y - 3) + "px"); maskNoteElement.style.setProperty("left", (boundingRectAnchor.x - 3) + "px"); maskNoteElement.style.setProperty("width", (boundingRectAnchor.width + 3) + "px"); maskNoteElement.style.setProperty("height", (boundingRectAnchor.height + 3) + "px"); } } function hideMaskNote() { maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS); if (selectedNote) { maskNoteElement.classList.remove(selectedNote.dataset.color); } } function selectNote(noteElement) { if (selectedNote) { selectedNote.classList.remove(NOTE_SELECTED_CLASS); maskNoteElement.classList.remove(selectedNote.dataset.color); } noteElement.classList.add(NOTE_SELECTED_CLASS); noteElement.classList.add(noteElement.dataset.color); selectedNote = noteElement; } function getTarget(clientX, clientY) { const targets = Array.from(document.elementsFromPoint(clientX, clientY)).filter(element => element.matches("html *:not(" + NOTE_TAGNAME + "):not(." + MASK_CLASS + ")")); if (!targets.includes(document.documentElement)) { targets.push(document.documentElement); } let newTarget, target = targets[0], boundingRect = target.getBoundingClientRect(); newTarget = determineTargetElement("floor", target, clientX - boundingRect.left, getMatchedParents(target, "left")); if (newTarget == target) { newTarget = determineTargetElement("ceil", target, boundingRect.left + boundingRect.width - clientX, getMatchedParents(target, "right")); } if (newTarget == target) { newTarget = determineTargetElement("floor", target, clientY - boundingRect.top, getMatchedParents(target, "top")); } if (newTarget == target) { newTarget = determineTargetElement("ceil", target, boundingRect.top + boundingRect.height - clientY, getMatchedParents(target, "bottom")); } target = newTarget; while (boundingRect = target && target.getBoundingClientRect(), boundingRect && boundingRect.width <= SELECT_PX_THRESHOLD && boundingRect.height <= SELECT_PX_THRESHOLD) { target = target.parentElement; } return target; } function getMatchedParents(target, property) { let element = target, matchedParent, parents = []; do { const boundingRect = element.getBoundingClientRect(); if (element.parentElement && !element.parentElement.tagName.toLowerCase() != NOTE_TAGNAME && !element.classList.contains(MASK_CLASS)) { const parentBoundingRect = element.parentElement.getBoundingClientRect(); matchedParent = Math.abs(parentBoundingRect[property] - boundingRect[property]) <= SELECT_PX_THRESHOLD; if (matchedParent) { if (element.parentElement.clientWidth > SELECT_PX_THRESHOLD && element.parentElement.clientHeight > SELECT_PX_THRESHOLD && ((element.parentElement.clientWidth - element.clientWidth > SELECT_PX_THRESHOLD) || (element.parentElement.clientHeight - element.clientHeight > SELECT_PX_THRESHOLD))) { parents.push(element.parentElement); } element = element.parentElement; } } else { matchedParent = false; } } while (matchedParent && element); return parents; } function determineTargetElement(roundingMethod, target, widthDistance, parents) { if (Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) <= parents.length) { target = parents[parents.length - Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) - 1]; } return target; } }; const anchorNote = function anchorNote(event, noteElement, deltaX, deltaY) { event.preventDefault(); const { clientX, clientY } = getPosition(event); document.documentElement.style.removeProperty("user-select"); noteElement.classList.remove(NOTE_MOVING_CLASS); maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS); maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS); maskNoteElement.classList.remove(noteElement.dataset.color); const headerElement = noteElement.querySelector("header"); headerElement.ontouchmove = document.documentElement.onmousemove = null; let currentElement = anchorElement; let positionedElement; while (currentElement.parentElement && !positionedElement) { if (!FORBIDDEN_TAG_NAMES.includes(currentElement.tagName.toLowerCase())) { const currentElementStyle = getComputedStyle(currentElement); if (currentElementStyle.position != "static") { positionedElement = currentElement; } } currentElement = currentElement.parentElement; } if (!positionedElement) { positionedElement = document.documentElement; } const containerElement = noteElement.getRootNode().host; if (positionedElement == document.documentElement) { const firstMaskElement = document.querySelector("." + MASK_CLASS); firstMaskElement.parentElement.insertBefore(containerElement, firstMaskElement); } else { positionedElement.appendChild(containerElement); } const boundingRectPositionedElement = positionedElement.getBoundingClientRect(); const stylePositionedElement = window.getComputedStyle(positionedElement); const borderX = parseInt(stylePositionedElement.getPropertyValue("border-left-width")); const borderY = parseInt(stylePositionedElement.getPropertyValue("border-top-width")); noteElement.style.setProperty("position", "absolute"); noteElement.style.setProperty("left", (clientX - boundingRectPositionedElement.x - deltaX - borderX) + "px"); noteElement.style.setProperty("top", (clientY - boundingRectPositionedElement.y - deltaY - borderY) + "px"); }; const getPosition = function getPosition(event) { if (event.touches && event.touches.length) { const touch = event.touches[0]; return touch; } else { return event; } }; const onMouseUp = function onMouseUp(event) { if (highlightSelectionMode) { event.preventDefault(); highlightSelection(); onUpdate(false); } if (removeHighlightMode) { event.preventDefault(); let element = event.target, done; while (element && !done) { if (element.classList.contains(HIGHLIGHT_CLASS)) { document.querySelectorAll("." + HIGHLIGHT_CLASS + "[data-singlefile-highlight-id=" + JSON.stringify(element.dataset.singlefileHighlightId) + "]").forEach(highlightedElement => { resetHighlightedElement(highlightedElement); onUpdate(false); }); done = true; } element = element.parentElement; } } if (resizingNoteMode) { event.preventDefault(); resizingNoteMode = false; document.documentElement.style.removeProperty("user-select"); maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS); document.documentElement.ontouchmove = onTouchMove; document.documentElement.onmousemove = null; onUpdate(false); } if (movingNoteMode) { event.preventDefault(); anchorNote(movingNoteMode.event || event, selectedNote, movingNoteMode.deltaX, movingNoteMode.deltaY); movingNoteMode = null; document.documentElement.ontouchmove = onTouchMove; document.documentElement.onmousemove = null; onUpdate(false); } if ((cuttingMode || cuttingOuterMode) && cuttingPath) { event.preventDefault(); if (event.ctrlKey) { const element = cuttingPath[cuttingPathIndex]; element.classList.toggle(cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS); } else { validateCutElement(event.shiftKey); } } if (collapseNoteTimeout) { clearTimeout(collapseNoteTimeout); collapseNoteTimeout = null; } }; const getShadowRoot = function getShadowRoot(element) { const chrome = window.chrome; if (element.openOrClosedShadowRoot) { return element.openOrClosedShadowRoot; } else if (chrome && chrome.dom && chrome.dom.openOrClosedShadowRoot) { try { return chrome.dom.openOrClosedShadowRoot(element); } catch (error) { return element.shadowRoot; } } else { return element.shadowRoot; } }; const waitResourcesLoad = function waitResourcesLoad() { return new Promise(resolve => { let counterMutations = 0; const done = () => { observer.disconnect(); resolve(); }; let timeoutInit = setTimeout(done, 100); const observer = new MutationObserver(() => { if (counterMutations < 20) { counterMutations++; clearTimeout(timeoutInit); timeoutInit = setTimeout(done, 100); } else { done(); } }); observer.observe(document, { subtree: true, childList: true, attributes: true }); }); }; const maskNoteElement = getMaskElement("note-mask"); const maskPageElement = getMaskElement("page-mask", "single-file-page-mask"); let selectedNote, highlightSelectionMode, removeHighlightMode, resizingNoteMode, movingNoteMode, collapseNoteTimeout, cuttingMode, cuttingOuterMode; window.onresize = reflowNotes; window.onUpdate = () => {}; document.documentElement.onmouseup = document.documentElement.ontouchend = onMouseUp; processNode(document); reflowNotes(); document.querySelectorAll("single-file-note").forEach(noteElement => attachNoteListeners(noteElement)); if (document.documentElement.dataset.sfz !== undefined) { waitResourcesLoad().then(reflowNotes); } })()</script>